function toArray(value) {
    if (Array.isArray(value)) {
        return value;
    }
    return [value];
}
/**
 *
 * @param {*} value
 * @param { {remove_duplicate_pattern: string, remove_duplicate_prefix: string, remove_duplicate_suffix: string} } options
 * @returns
 */
function removeDuplicate(value, options) {
    const seen = {};
    const duplicate_map = {};
    options = options || {};
    value = value.filter((v) => {
        const raw = (v = typeof v === "string" ? v : JSON.stringify(v));
        if (options.remove_duplicate_pattern) {
            v = v.match(new RegExp(options.remove_duplicate_pattern));
        }
        duplicate_map[raw] = v;
        if (!seen[v]) {
            seen[v] = 1;
            return true;
        } else {
            ++seen[v];
        }
    });
    // // 添加过滤前缀
    if (options.remove_duplicate_prefix || options.remove_duplicate_suffix) {
        value = value.map((v) => {
            const raw = typeof v === "string" ? v : JSON.stringify(v);
            const repeat = seen[duplicate_map[raw]] || 0;
            const prefix =
                (options.remove_duplicate_prefix &&
                    format(options.remove_duplicate_prefix, { repeat })) ||
                "";
            const suffix =
                (options.remove_duplicate_suffix &&
                    format(options.remove_duplicate_suffix, { repeat })) ||
                "";
            return prefix + v + suffix;
        });
    }
    return value;
}

function removeDuplicateObject(value, key) {
    const seen = {};
    value = value.filter((v) => {
        v = getDataByKeySimple(key, v);
        if (!seen[v]) {
            seen[v] = 1;
            return true;
        } else {
            ++seen[v];
        }
    });
    return value;
}

/**
 * 格式化字符串
 * @param {*} value
 * @param {*} args
 */
function format(value, ...args) {
    if (args.length === 1 && typeof args[0] === "object") {
        args = args[0];
    }
    return value.replace(/\{(\S+?)\}/g, (s, i) =>
        args && args[i] == null ? s : args[i],
    );
}

const converters = {
    int: parseInt,
    integer: parseInt,
    float: parseFloat,
    number: parseFloat,
    string: (v) => v.toString(),
    str: (v) => v.toString(),
    list: (v) => (Array.isArray(v) ? v : JSON.parse(v)),
    array: (v) => (Array.isArray(v) ? v : JSON.parse(v)),
    bool: (v) => !!v,
    boolean: (v) => !!v,
    millisecond: (v) => {
        if (typeof v === "number") {
            return v;
        }
        if (typeof v === "string") {
            const fragments = v.split(":");
            let result = 0;
            fragments.forEach((fragment, i) => {
                result += parseFloat(fragment) * 60 ** (2 - i) * 1000;
            });
            return result;
        }
        throw `无法将 ${v} 转换成 millisecond 类型`;
    },
};
/**
 *
 * @param {*} value
 * @param {"int" | "integer" | "float" | "number" | "string" | "str" | "list" | "array" | "bool" | "boolean" | "millisecond" } type
 * @returns
 */
function convert(value, type) {
    const value_type_matches = /(\w+)((\[\])*)/.exec(type || "");
    const main_type = value_type_matches?.[1];
    // n维数组
    const num_dim =
        (value_type_matches?.[2] && value_type_matches[2].length / 2) || 0;
    const converter = converters[main_type];
    if (converter) {
        value = recursive_convert_list(value, converter, num_dim);
    }
    return value;
}

function recursive_convert_list(value, converter, num_dim) {
    if (!num_dim) {
        return converter(value);
    } else {
        for (const i in value) {
            const v = value[i];
            value[i] = recursive_convert_list(v, converter, num_dim - 1);
        }
        return value;
    }
}

/**
 * JSON字符串化
 * @param {Array|Object} value 对象
 * @param {number} space 空格数，可选。默认值：4
 * @param {string[]?} sortKeys 键排序数据，可选。默认值：null
 */
function json_compacted(value, space, sortKeys) {
    if (value == null) {
        return "null";
    }
    let result = "";
    space = space === undefined ? 2 : space;
    let depth = arguments[3] === undefined ? 0 : arguments[3];

    /** key: value 是否换行 */
    let wrapKV = false;
    /** 对象属性 是否换行 */
    let wrapAtt = false;
    /** 数组元素 是否换行 */
    let wrapArr = false;
    wrapKV = wrapAtt = wrapArr = space > 0;

    /** 是否可以显示在同一行 */
    let canSameLine = false;
    if (Array.isArray(value)) {
        canSameLine = value.every((v) => typeof v !== "object");
        if (canSameLine) {
            wrapKV = false;
            wrapArr = false;
        }
    } else if (typeof value === "object") {
        canSameLine = true;
        for (const k in value) {
            if (Object.hasOwn(value, k)) {
                if (value[k] != null && typeof value[k] === "object") {
                    canSameLine = false;
                    break;
                }
            }
        }
        if (canSameLine) {
            wrapAtt = false;
            wrapKV = false;
        }
    }
    let space1 = "";
    let n = (depth + 1) * space;
    while (--n >= 0) {
        space1 += " ";
    }
    const space2 = space1.substr(space);

    if (Array.isArray(value)) {
        result += "[";
        if (wrapKV) {
            result += "\n";
        }
        ++depth;
        if (!wrapArr && wrapKV) {
            result += space1;
        }
        // 不能用 map 需要用 for。 使用 for 时，如果value有<empty item>, map 无法正确的值
        for (let i = 0, len = value.length; i < len; ++i) {
            const v = value[i];
            result += `${wrapArr ? space1 : ""}${json_compacted(v, space, sortKeys, depth)}`;
            if (i !== len - 1) {
                result += !wrapArr ? "," : ",\n";
            }
        }
        // result += value.map(v => {
        //     return `${wrapArr ? space1 : ""}${json_compacted(v, space, sortKeys, depth)}`;
        // }).join(!wrapArr ? "," : ",\n");
    } else if (typeof value === "object") {
        result += "{";
        if (wrapKV) {
            result += "\n";
        }
        ++depth;
        let keys = Object.keys(value);
        keys = keys.filter((v) => Object.hasOwn(value, v));
        if (Array.isArray(sortKeys)) {
            const num = sortKeys.length * 2;
            keys.sort((keyA, keyB) => {
                let idxA = sortKeys.indexOf(keyA);
                let idxB = sortKeys.indexOf(keyB);
                idxA = (idxA + num) % num;
                idxB = (idxB + num) % num;
                return idxA - idxB;
            });
        }
        if (!wrapAtt && wrapKV) {
            result += space1;
        }
        result += keys
            .map((v) => {
                return `${wrapAtt ? space1 : ""}"${v}": ${json_compacted(value[v], space, sortKeys, depth)}`;
            })
            .join(!wrapAtt ? "," : ",\n");
    } else if (typeof value === "string") {
        result += `"${escapeString(value)}"`;
    } else {
        result += value;
    }
    if (Array.isArray(value)) {
        if (wrapKV) {
            result += "\n";
            result += space2;
        }
        result += "]";
    } else if (typeof value === "object") {
        if (wrapKV) {
            result += "\n";
            result += space2;
        }
        result += "}";
    }
    return result;
}

function escapeString(str) {
    return (
        str
            .replace(/\\/g, "\\\\")
            .replace(/"/g, '\\"')
            // .replace(/\//g, '\\/')
            // *注: [\b] 是匹配backspace, \b 是匹配边界
            .replace(/[\b]/g, "\\b")
            .replace(/\f/g, "\\f")
            .replace(/\n/g, "\\n")
            .replace(/\r/g, "\\r")
            .replace(/\t/g, "\\t")
    );
}

/** 格式化数值相关 */
function formatByValues(values, fmt, prefix) {
    prefix = prefix || "0000";
    return fmt.replace(/(.)\1{0,}/g, (matchString) => {
        var key = matchString[0];
        if (key && Object.hasOwn(values, key)) {
            var value = values[key];
            var valueStr = value.toString();
            if (matchString.length === 1) {
                return valueStr;
            } else {
                return (prefix + valueStr).substr(
                    valueStr.length + prefix.length - matchString.length,
                );
            }
        } else {
            return matchString;
        }
    });
}

const dayInWeeks = ["日", "一", "二", "三", "四", "五", "六"];
const fullDayInWeeks = [
    "星期日",
    "星期一",
    "星期二",
    "星期三",
    "星期四",
    "星期五",
    "星期六",
];
/**
 * 格式化日期
 * @param {Date} date
 * @param {string} fmt
 */
function formatDate(date, fmt) {
    if (fmt == null) {
        // fmt = 'YYYY-MM-DD hh:mm:ss SSS';
        fmt = "YYYY-MM-DD hh:mm:ss";
    }

    var values = {
        Y: date.getFullYear(),
        M: date.getMonth() + 1,
        D: date.getDate(),
        e: dayInWeeks[date.getDay()],
        E: fullDayInWeeks[date.getDay()],
        h: date.getHours(),
        m: date.getMinutes(),
        s: date.getSeconds(),
        S: date.getMilliseconds(),
    };
    return formatByValues(values, fmt);
}

/**
 * 格式化时间戳(毫秒)
 * @param {number} millisec 毫秒
 * @param {string} fmt
 */
function formatTimestampMillisec(millisec, fmt) {
    /**
     * @type {Date}
     */
    const date = (this.__formatDateInMillisec__date =
        this.__formatDateInMillisec__date || new Date());
    date.setTime(millisec);
    return formatDate(date, fmt);
}

/**
 * 格式化时间戳(秒)
 * @param {number} sec 秒
 * @param {string} fmt
 */
function formatTimestampSec(sec, fmt) {
    return formatTimestampMillisec(sec * 1000, fmt);
}

/**
 * 格式化时间（秒）
 * @param {number} time
 * @param {string} fmt
 */
function formatTimeInSec(time, fmt) {
    if (fmt == null) {
        fmt = "hh:mm:ss";
    }

    var hours = Math.floor(time / 3600),
        minutes = Math.floor(time / 60) % 60,
        seconds = Math.floor(time % 60);
    var values = {
        h: hours,
        m: minutes,
        s: seconds,
    };
    return formatByValues(values, fmt);
}

/**
 * 格式化时间（毫秒）
 * @param {number} time
 * @param {string | boolean } fmt
 * @param {{ hour: string, minute: string, second: string, millisec: string }} fmtData
 */
function formatTimeInMillisec(time, fmt, fmtData) {
    if (fmt == null) {
        fmt = "hh:mm:ss SSS";
    }
    if (fmt === true) {
        fmt = fmtData || {
            day: "天",
            hour: "小时",
            minute: "分钟",
            second: "秒",
            millisec: "毫秒",
        };
    }

    var days = Math.floor(time / 86400000);
    var hours = Math.floor(time / 3600000) % 24;
    var seconds = Math.floor(time / 1000) % 60;
    var minutes = Math.floor(time / 60000) % 60;
    var millisec = time % 1000;
    var values = {
        d: days,
        h: hours,
        m: minutes,
        s: seconds,
        S: millisec,
    };
    if (typeof fmt === "object") {
        if (time < 1000) {
            return formatByValues(values, `S${fmt.millisec}`);
        }
        if (time < 60 * 1000) {
            return formatByValues(values, `s${fmt.second}`);
        }
        if (time < 60 * 1000 * 60) {
            return formatByValues(values, `m${fmt.minute}`);
        }
        return formatByValues(values, `h${fmt.hour}`);
    }
    return formatByValues(values, fmt);
}

/** 对齐方式 */
const Alignment = {
    RIGHT: 1, // 右对齐
    LEFT: 2, // 左对齐
    CENTER: 3, // 居中对齐
};

/**
 * 使用from的格式化长度与to的格式化长度相等
 * @param {*} from
 * @param {*} to
 * @param { {alignment: Alignment, paddingChar: string} } options
 */
function alignNumber(from, to, options) {
    return alignStr(from, to.toString().length, options);
}

/**
 * 使用from的格式化长度与toNum相等
 * @param {*} from
 * @param {number} toNum
 * @param { {alignment: Alignment, paddingChar: string} } options
 */
function alignStr(from, toNum, options) {
    options = Object.assign(
        { alignment: Alignment.RIGHT, paddingChar: " " },
        options,
    );
    let prefixNum = 0;
    let suffixNum = 0;
    from = from.toString().substr(0, toNum);
    const fromNum = from.length;
    let totalOffset = toNum - fromNum;
    if (totalOffset < 0) {
        totalOffset = 0;
    }
    switch (options.alignment) {
        case Alignment.LEFT: {
            suffixNum = totalOffset;
            break;
        }
        case Alignment.CENTER: {
            prefixNum = Math.ceil(totalOffset / 2);
            suffixNum = Math.floor(totalOffset / 2);
            break;
        }
        default: {
            prefixNum = totalOffset;
            break;
        }
    }
    return (
        options.paddingChar.repeat(prefixNum) +
        from +
        options.paddingChar.repeat(suffixNum)
    );
}

/**
 * 扁平化数组
 * @param {*} src
 * @param {any[]} dst
 */
function flatten(src, dst = null) {
    if (dst == null) {
        dst = [];
    }
    if (Array.isArray(src)) {
        for (const sub_src of src) {
            flatten(sub_src, dst);
        }
    } else {
        dst.push(src);
    }
    return dst;
}

const KB = 1024;
const MB = 1024 * 1024;
const GB = 1024 * 1024 * 1024;

/**
 *
 * @param {number} bytes
 */
function formatByteSize(bytes) {
    if (bytes >= GB) {
        return `${(bytes / GB).toFixed(2)} GB`;
    }
    if (bytes >= MB) {
        return `${(bytes / MB).toFixed(2)} MB`;
    }
    return `${(bytes / KB).toFixed(2)} KB`;
}

function getByteSize(formatedStr) {
    formatedStr = formatedStr.toString().trim();
    let bytes = 0;
    if (formatedStr.match(/gb?$/i)) {
        bytes = Number(formatedStr.replace(/gb?$/i, "")) * GB;
    } else if (formatedStr.match(/mb?$/i)) {
        bytes = Number(formatedStr.replace(/mb?$/i, "")) * MB;
    } else if (formatedStr.match(/kb?$/i)) {
        bytes = Number(formatedStr.replace(/kb?$/i, "")) * KB;
    } else {
        bytes = Number(formatedStr.replace(/b?$/i, ""));
    }
    return bytes;
}

/**
 *
 * @param {string} key
 */
function getDataByKeySimple(key, from) {
    const subkeys = key.split(".");
    let val = from;
    for (const subkey of subkeys) {
        if (subkey == null) {
            continue;
        }
        val = val?.[subkey];
    }
    return val;
}

module.exports = {
    toArray,
    format,
    convert,
    json_compacted,
    formatByValues,
    formatDate,
    formatTimestampMillisec,
    formatTimestampSec,
    formatTimeInSec,
    formatTimeInMillisec,
    alignNumber,
    Alignment,
    flatten,
    alignStr,
    formatByteSize,
    escapeString,
    removeDuplicate,
    removeDuplicateObject,
    getByteSize,
};
